import { addError, ellipsify } from 'markdownlint-rule-helpers'

import { getRange } from '@/content-linter/lib/helpers/utils'
import frontmatter from '@/frame/lib/read-frontmatter'

import type { RuleParams, RuleErrorCallback } from '@/content-linter/types'

// Mapping of outdated terms to their new replacements
// Order matters - longer phrases must come first to avoid partial matches
const TERMINOLOGY_REPLACEMENTS: [string, string][] = [
  // Beta variations → public preview (longer phrases first)
  ['limited public beta', 'public preview'],
  ['public beta', 'public preview'],
  ['private beta', 'private preview'],
  ['beta', 'public preview'],

  // Alpha → private preview
  ['alpha', 'private preview'],

  // Deprecated variations → closing down
  ['deprecation', 'closing down'],
  ['deprecated', 'closing down'],

  // Sunset → retired
  ['sunset', 'retired'],
]

// Don't lint filepaths that have legitimate uses of these terms
const EXCLUDED_PATHS: string[] = [
  // Individual files
  'content/actions/reference/runners/github-hosted-runners.md',
  'content/actions/reference/workflows-and-actions/metadata-syntax.md',
  'content/admin/administering-your-instance/administering-your-instance-from-the-command-line/command-line-utilities.md',
  'content/authentication/managing-commit-signature-verification/checking-for-existing-gpg-keys.md',
  'content/codespaces/setting-your-user-preferences/choosing-the-stable-or-beta-host-image.md',
  'content/rest/using-the-rest-api/getting-started-with-the-rest-api.md',
  'data/reusables/actions/jobs/choosing-runner-github-hosted.md',
  'data/reusables/code-scanning/codeql-query-tables/cpp.md',
  'data/reusables/dependabot/dependabot-updates-supported-versioning-tags.md',
  'data/variables/release-phases.yml',
  'data/release-notes/enterprise-server/3-17/0-rc1.yml',
  // Directories
  'content/site-policy/',
  'data/features/',
  'data/release-notes/enterprise-server/3-14/',
  'data/release-notes/enterprise-server/3-15/',
]

interface CompiledRegex {
  regex: RegExp
  outdatedTerm: string
  replacement: string
}

interface MatchInfo {
  start: number
  end: number
  text: string
  replacement: string
  outdatedTerm: string
}

// Precompile RegExp objects for better performance
const COMPILED_REGEXES: CompiledRegex[] = TERMINOLOGY_REPLACEMENTS.map(
  ([outdatedTerm, replacement]) => ({
    regex: new RegExp(`(?<!\\w|-|_)${outdatedTerm.replace(/\s+/g, '\\s+')}(?!\\w|-|_)`, 'gi'),
    outdatedTerm,
    replacement,
  }),
)

/**
 * Find all non-overlapping matches of outdated terminology in a line
 * @param line - The line of text to search
 * @returns Array of match objects with start, end, text, replacement, and outdatedTerm
 */
function findOutdatedTerminologyMatches(line: string): MatchInfo[] {
  const foundMatches: MatchInfo[] = []

  // Check each outdated term (in order - longest first)
  for (const { regex, outdatedTerm, replacement } of COMPILED_REGEXES) {
    // Reset regex state for each line
    regex.lastIndex = 0
    let match: RegExpExecArray | null

    while ((match = regex.exec(line)) !== null) {
      // Check if this match overlaps with any existing matches
      const overlaps = foundMatches.some(
        (existing) =>
          (match!.index >= existing.start && match!.index < existing.end) ||
          (match!.index + match![0].length > existing.start &&
            match!.index + match![0].length <= existing.end) ||
          (match!.index <= existing.start && match!.index + match![0].length >= existing.end),
      )

      if (!overlaps) {
        foundMatches.push({
          start: match.index,
          end: match.index + match[0].length,
          text: match[0],
          replacement,
          outdatedTerm,
        })
      }
    }
  }

  // Sort matches by position for consistent ordering
  return foundMatches.sort((a, b) => a.start - b.start)
}

export const outdatedReleasePhaseTerminology = {
  names: ['GHD046', 'outdated-release-phase-terminology'],
  description:
    'Outdated release phase terminology should be replaced with current GitHub terminology',
  tags: ['terminology', 'consistency', 'release-phases'],
  severity: 'error',
  function: (params: RuleParams, onError: RuleErrorCallback) => {
    // Skip excluded files
    for (const filepath of EXCLUDED_PATHS) {
      if (params.name.startsWith(filepath)) {
        return
      }
    }

    // Skip autogenerated files
    const frontmatterString = params.frontMatterLines.join('\n')
    const fm = frontmatter(frontmatterString).data
    if (fm && fm.autogenerated) return

    // Check all lines for outdated terminology
    for (let i = 0; i < params.lines.length; i++) {
      const line = params.lines[i]
      const lineNumber = i + 1

      // Find all matches on this line
      const foundMatches = findOutdatedTerminologyMatches(line)

      // Report all found matches
      for (const matchInfo of foundMatches) {
        const range = getRange(line, matchInfo.text)
        const errorMessage = `Replace outdated terminology "${matchInfo.text}" with "${matchInfo.replacement}"`

        // Provide a fix suggestion
        const fixInfo = {
          editColumn: matchInfo.start + 1,
          deleteCount: matchInfo.text.length,
          insertText: matchInfo.replacement,
        }

        addError(onError, lineNumber, errorMessage, ellipsify(line), range, fixInfo)
      }
    }
  },
}
